/**
 * Copyright (C) 2016-2019 DSpot Sp. z o.o
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.dspot.declex.override.holder;

import static com.helger.jcodemodel.JExpr._null;
import static com.helger.jcodemodel.JExpr._this;
import static com.helger.jcodemodel.JExpr.cast;
import static com.helger.jcodemodel.JExpr.invoke;
import static com.helger.jcodemodel.JExpr.lit;
import static org.ohosannotations.helper.ModelConstants.generationSuffix;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.VariableElement;

import org.ohosannotations.annotations.EBean;
import org.ohosannotations.annotations.RootContext;
import org.ohosannotations.helper.APTCodeModelHelper;
import org.ohosannotations.helper.CanonicalNameConstants;
import org.ohosannotations.holder.EFractionHolder;

import org.ohosannotations.rclass.IRClass.Res;
import org.ohosannotations.OhosAnnotationsEnvironment;
import org.ohosannotations.plugin.PluginClassHolder;

import com.dspot.declex.action.Actions;
import com.dspot.declex.annotation.action.ActionFor;
import com.dspot.declex.api.action.process.ActionInfo;
import com.dspot.declex.api.action.process.ActionMethodParam;
import com.dspot.declex.override.helper.DeclexAPTCodeModelHelper;
import com.dspot.declex.util.DeclexConstant;
import com.dspot.declex.util.JavaDocUtils;
import com.dspot.declex.util.TypeUtils;
import com.helger.jcodemodel.AbstractJClass;
import com.helger.jcodemodel.JAnnotationUse;
import com.helger.jcodemodel.JConditional;
import com.helger.jcodemodel.JDefinedClass;
import com.helger.jcodemodel.JFieldRef;
import com.helger.jcodemodel.JFieldVar;
import com.helger.jcodemodel.JInvocation;
import com.helger.jcodemodel.JMethod;
import com.helger.jcodemodel.JMod;
import com.helger.jcodemodel.JVar;

/**
 * 片段行动持有人
 *
 * @author ohos
 * @date 2021/07/27
 */
public class FragmentActionHolder extends PluginClassHolder<EFractionHolder> {
    /**
     * 容器名称
     */
    private final static String CONTAINER_NAME = "container";
    /**
     * 交易名称
     */
    private final static String TRANSACTION_NAME = "transaction";
    /**
     * 更换名字
     */
    private final static String REPLACE_NAME = "replace";
    /**
     * 添加名字
     */
    private final static String ADD_NAME = "add";
    /**
     * init的名字
     */
    private final static String INIT_NAME = "init";
    /**
     * 构建的名字
     */
    private final static String BUILD_NAME = "build";
    /**
     * 执行的名字
     */
    private final static String EXECUTE_NAME = "execute";
    /**
     * 添加到堆栈的名字
     */
    private final static String ADD_TO_BACK_STACK_NAME = "pushIntoStack";
    /**
     * 建造者的名字
     */
    private final static String BUILDER_NAME = "builder";

    /**
     * 片段的行动
     */
    public JDefinedClass FragmentAction;
    /**
     * 上下文字段
     */
    private JFieldVar contextField;
    /**
     * 开始现场
     */
    private JFieldVar startedField;
    /**
     * 施工现场
     */
    private JFieldVar builderField;
    /**
     * 标记字段
     */
    private JFieldVar tagField;
    /**
     * init方法
     */
    private JMethod initMethod;
    /**
     * 容器领域
     */
    private JFieldVar containerField;
    /**
     * 事务领域
     */
    private JFieldVar transactionField;
    /**
     * 交易方法领域
     */
    private JFieldVar transactionMethodField;

    /**
     * 片段行动持有人
     *
     * @param holder 持有人
     */
    public FragmentActionHolder(EFractionHolder holder) {
        super(holder);
    }

    /**
     * 创建信息操作
     *
     * @param element 元素
     * @param env env
     */
    public static void createInformationForActionHolder(Element element, OhosAnnotationsEnvironment env) {

        final String clsName = element.asType().toString();
        final int index = clsName.lastIndexOf('.');
        final String pkg = clsName.substring(0, index);
        final String fragmentName = clsName.substring(index + 1);
        final String actionName = pkg + "." + fragmentName + "ActionHolder";

        ActionInfo actionInfo = new ActionInfo(actionName);
        actionInfo.isTimeConsuming = false;

        //This will avoid generation for parent classes, not used in the project
        actionInfo.generated = false;

        actionInfo.setReferences(JavaDocUtils.referenceFromClassName(clsName));
        Actions.getInstance().addAction(fragmentName, actionName, actionInfo);

        actionInfo.addMethod(CONTAINER_NAME, actionName);

        //FragmentTransaction can change the package
        actionInfo.addMethod(TRANSACTION_NAME, "ohos.aafwk.ability.fraction.FractionScheduler");

        actionInfo.addMethod(REPLACE_NAME, actionName);

        actionInfo.addMethod(ADD_NAME, actionName);

        actionInfo.addMethod(INIT_NAME, env.getCodeModel().VOID.fullName());
        actionInfo.addMethod(
            INIT_NAME,
            env.getCodeModel().VOID.fullName(),
            Arrays.asList(new ActionMethodParam("tag", env.getClasses().STRING))
        );

        actionInfo.addMethod(
            BUILD_NAME,
            env.getCodeModel().VOID.fullName(),
            Arrays.asList(new ActionMethodParam("Started", env.getJClass(Runnable.class)))
        );

        actionInfo.addMethod(EXECUTE_NAME, env.getCodeModel().VOID.fullName());

        actionInfo.addMethod(ADD_TO_BACK_STACK_NAME, actionName);

        actionInfo.addMethod(BUILDER_NAME, actionName + ".FragmentBuilder" + generationSuffix());

    }

    /**
     * 片段添加参数
     *
     * @param actionInfo 行动信息
     * @param element 元素
     * @param env env
     */
    public static void addFragmentArg(ActionInfo actionInfo, Element element, OhosAnnotationsEnvironment env) {

        APTCodeModelHelper codeModelHelper = new DeclexAPTCodeModelHelper(env);

        final String clsName = element.getEnclosingElement().asType().toString();
        final int index = clsName.lastIndexOf('.');
        final String pkg = clsName.substring(0, index);
        final String fragmentName = clsName.substring(index + 1);
        final String actionName = pkg + "." + fragmentName + "ActionHolder";

        final String elementName = element.getSimpleName().toString();
        if (element.getKind().isField()) {
            final AbstractJClass clazz = codeModelHelper.elementTypeToJClass(element);
            actionInfo.addMethod(
                elementName,
                actionName,
                Arrays.asList(new ActionMethodParam(elementName, clazz))
            );

        } else if (element.getKind() == ElementKind.METHOD) {

            List<? extends VariableElement> elementParams = ((ExecutableElement) element).getParameters();
            List<ActionMethodParam> params = new ArrayList<>(elementParams.size());

            for (VariableElement param : elementParams) {
                final String paramName = param.getSimpleName().toString();
                final AbstractJClass paramClass = codeModelHelper.elementTypeToJClass(param);
                params.add(new ActionMethodParam(paramName, paramClass));
            }

            actionInfo.addMethod(elementName, actionName, params);
        }
    }

    /**
     * 得到片段行动
     *
     * @return {@link JDefinedClass}
     */
    public JDefinedClass getFragmentAction() {
        if (FragmentAction == null) {
            setFragmentAction();
        }
        return FragmentAction;
    }

    /**
     * 设置片段行动
     */
    private void setFragmentAction() {
        try {
            String clsName = holder().getAnnotatedElement().asType().toString();

            int index = clsName.lastIndexOf('.');
            String pkg = clsName.substring(0, index);
            String fragmentName = clsName.substring(index + 1);
            String actionName = pkg + "." + fragmentName + "ActionHolder";

            FragmentAction = getCodeModel()._getClass(actionName);
            if (FragmentAction == null) {

                FragmentAction = getCodeModel()._class(actionName);
                FragmentAction.annotate(EBean.class);
                JAnnotationUse actionFor = FragmentAction.annotate(ActionFor.class);
                actionFor.param("timeConsuming", false);
                actionFor.param("value", fragmentName);

                ActionInfo actionInfo = Actions.getInstance().getActionInfos().get(FragmentAction.fullName());
                actionInfo.generated = true;
                FragmentAction.javadoc().add(actionInfo.references);

                setFields();

                setInit();
                setContainer();
                setTransaction();
                setTransactionMethods();

                setBuild();
                setExecute();

                setBuilder();
                setAddToBackStack();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 设置容器
     */
    private void setContainer() {
        JFieldRef containerRes = environment().getRClass().getIdStaticRef(CONTAINER_NAME, environment());
        if (containerRes != null) {
            containerField = FragmentAction.field(JMod.PRIVATE, getCodeModel().INT, CONTAINER_NAME, containerRes);
        } else {
            containerField = FragmentAction.field(JMod.PRIVATE, getCodeModel().INT, CONTAINER_NAME);
        }

        JMethod containerMethod = FragmentAction.method(JMod.PUBLIC, FragmentAction, CONTAINER_NAME);
        JVar containerParam = containerMethod.param(getCodeModel().INT, CONTAINER_NAME);
        containerMethod.body().assign(_this().ref(containerField), containerParam);
        containerMethod.body()._return(_this());
    }

    /**
     * 设置事务
     */
    private void setTransaction() {
        AbstractJClass ABILITY = environment().getClasses().FRAGMENT_ABILITY;
        AbstractJClass FragmentTransaction = getJClass("ohos.aafwk.ability.fraction.FractionScheduler");

        String getFractionManager = "getFractionManager";

        transactionField = FragmentAction.field(JMod.PRIVATE, FragmentTransaction, TRANSACTION_NAME);

        JConditional ifIsAbility = initMethod.body()._if(contextField._instanceof(ABILITY));
        ifIsAbility._then().assign(
            transactionField,
            invoke(cast(ABILITY, contextField), getFractionManager).invoke("startFractionScheduler")
        );

        JMethod transactionMethodMethod = FragmentAction.method(JMod.PUBLIC, FragmentTransaction, TRANSACTION_NAME);
        transactionMethodMethod.body()._return(transactionField);
    }

    /**
     * 设置事务的方法
     */
    private void setTransactionMethods() {
        JMethod replaceMethod = FragmentAction.method(JMod.PUBLIC, FragmentAction, REPLACE_NAME);
        replaceMethod.body().assign(_this().ref(transactionMethodField), lit(0));
        replaceMethod.body()._return(_this());

        JMethod addMethod = FragmentAction.method(JMod.PUBLIC, FragmentAction, ADD_NAME);
        addMethod.body().assign(_this().ref(transactionMethodField), lit(1));
        addMethod.body()._return(_this());
    }

    /**
     * 设置字段
     */
    private void setFields() {
        contextField = FragmentAction.field(JMod.NONE, environment().getClasses().CONTEXT, "context");
        contextField.annotate(RootContext.class);

        startedField = FragmentAction.field(JMod.PRIVATE, getJClass(Runnable.class), "Started");
        builderField = FragmentAction.field(JMod.PRIVATE, holder().getBuilderClass(), "builder");

        tagField = FragmentAction.field(JMod.PRIVATE, getClasses().STRING, "tag");

        transactionMethodField = FragmentAction.field(JMod.PRIVATE, getCodeModel().INT, "transactionMethod", lit(0));
    }

    /**
     * 设置初始化
     */
    private void setInit() {
        initMethod = FragmentAction.method(JMod.NONE, getCodeModel().VOID, INIT_NAME);
        initMethod.body().add(invoke(INIT_NAME).arg(_null()));

        initMethod = FragmentAction.method(JMod.NONE, getCodeModel().VOID, INIT_NAME);
        JVar tagParam = initMethod.param(getClasses().STRING, "tag");
        initMethod.body().assign(_this().ref("tag"), tagParam);
        initMethod.body().assign(builderField, getGeneratedClass().staticInvoke("builder"));
    }

    /**
     * 集构建
     */
    private void setBuild() {
        JMethod buildMethod = FragmentAction.method(JMod.NONE, getCodeModel().VOID, BUILD_NAME);
        JVar startedParam = buildMethod.param(getJClass(Runnable.class), "Started");
        buildMethod.body().assign(_this().ref(startedField), startedParam);
    }

    /**
     * 设置执行
     */
    private void setExecute() {
        JMethod executeMethod = FragmentAction.method(JMod.NONE, getCodeModel().VOID, EXECUTE_NAME);

        executeMethod.body()._if(transactionField.eq(_null()))._then()._return();

        JInvocation transactionReplaceInvoke = transactionField.invoke("replace").arg(containerField)
            .arg(builderField.invoke("build"))
            .invoke("submit");

        JInvocation transactionAddInvoke = transactionField.invoke("add").arg(containerField)
            .arg(builderField.invoke("build")).arg(tagField)
            .invoke("submit");

        JConditional ifReplace = executeMethod.body()._if(transactionMethodField.eq(lit(0)));
        ifReplace._then().add(transactionReplaceInvoke);
        ifReplace._elseif(transactionMethodField.eq(lit(1)))._then().add(transactionAddInvoke);

        executeMethod.body()._if(startedField.ne(_null()))._then().invoke(startedField, "run");
    }

    /**
     * 设置添加到堆栈
     */
    private void setAddToBackStack() {
        JMethod addToBackStackMethod = FragmentAction.method(JMod.PUBLIC, FragmentAction, ADD_TO_BACK_STACK_NAME);
        JVar tag = addToBackStackMethod.param(getClasses().STRING, "tag");
        addToBackStackMethod.body()._if(transactionField.neNull())._then()
            .add(invoke(transactionField, "pushIntoStack").arg(tag));
        addToBackStackMethod.body()._return(_this());

        JMethod addToBackStackNullMethod = FragmentAction.method(JMod.PUBLIC, FragmentAction, ADD_TO_BACK_STACK_NAME);
        addToBackStackNullMethod.body().add(invoke(addToBackStackMethod).arg(_null()));
        addToBackStackNullMethod.body()._return(_this());

    }

    /**
     * 设置构建器
     */
    private void setBuilder() {
        JMethod builderMethodMethod = FragmentAction.method(JMod.PUBLIC, holder().getBuilderClass(), BUILDER_NAME);
        builderMethodMethod.body()._return(builderField);
    }

}
